<?php

declare(strict_types=1);
/**
 * MineAdmin is committed to providing solutions for quickly building web applications
 * Please view the LICENSE file that was distributed with this source code,
 * For the full copyright and license information.
 * Thank you very much for using MineAdmin.
 *
 * @Author X.Mo<root@imoi.cn>
 * @Link   https://gitee.com/xmo/MineAdmin
 */

namespace Plugin\Attractions\Commerce\Service\Commerce_order;

use App\Exception\BusinessException;
use App\Http\Common\ResultCode;
use Hyperf\Coroutine\Coroutine;
use Plugin\Attractions\Commerce\Repository\Commerce_order\CommerceOrderRepository;
use App\Service\IService;
use Common\Common;
use Hyperf\DbConnection\Db;
use Plugin\Attractions\Commerce\Common\IDGenerator;
use Plugin\Attractions\Commerce\Common\Type;
use Plugin\Attractions\Commerce\Model\Commerce_merchant\CommerceMerchant;
use Plugin\Attractions\Commerce\Model\Commerce_order\CommerceOrder;
use Plugin\Attractions\Commerce\Repository\Commerce_commodity\CommerceCommodityRepository;
use Plugin\Attractions\Commerce\Repository\Commerce_merchant\CommerceMerchantRepository;
use Plugin\Attractions\Commerce\Repository\Commerce_order_product\CommerceOrderProductRepository;
use Plugin\Attractions\Commerce\Request\Commerce_merchant\CommerceMerchantRequest;

/**
 * 订单服务类
 */
final class CommerceOrderService extends IService
{
    public function __construct(
        protected readonly CommerceOrderRepository $repository,
        protected readonly CommerceCommodityRepository $commodityRepository,
        protected readonly CommerceOrderProductRepository $orderProductRepository,
        protected readonly CommerceMerchantRepository $merchantRepository
    ) {}

    public function placeOrder(int $customer_id, array $data)
    {
        $merchantId = $data['merchant_id'];
        $commodity = $data['commodity'];
        $orderID = IDGenerator::getID(IDGenerator::ORDER);
        Db::beginTransaction();
        try {
            $quantity = abs(intval($commodity['quantity']));
            $id = $commodity['id'];
            $lockKey = "attraction:commerce:order:lock:$id";
            if (! Common::lock($lockKey, $orderID)) {
                throw new BusinessException(ResultCode::UNPROCESSABLE_ENTITY, trans('commerce.order.systemIsBusy'));
            }
            // 启动锁续期协程
            $renewCoroutineRunning = true;
            Coroutine::create(function () use ($lockKey, $orderID, &$renewCoroutineRunning) {
                while ($renewCoroutineRunning) {
                    Coroutine::sleep(4); // 每4秒续期一次
                    Common::renewLock($lockKey, $orderID);
                }
            });

            $commodityData = $this->commodityRepository->findById($id);
            if (empty($commodityData)) {
                throw new BusinessException(ResultCode::UNPROCESSABLE_ENTITY, trans('commerce.order.commodityError'));
            }
            $commodityData = $commodityData->toArray();
            // 扣除库存，增加销量
            $currentStockQuantity = $commodityData['stock_quantity'];
            if ($currentStockQuantity - $quantity < 0) {
                throw new BusinessException(ResultCode::UNPROCESSABLE_ENTITY, trans('commerce.order.stockNotEnough'));
            }
            $updateResult = $this->commodityRepository->getQuery()->where('id', $id)
                ->where('status', 1)
                ->update(
                    [
                        'stock_quantity' => $currentStockQuantity - $quantity,
                        'sales_quantity' => $commodityData['sales_quantity'] + $quantity,
                    ]
                );
            if ($updateResult === 0) {
                throw new BusinessException(ResultCode::UNPROCESSABLE_ENTITY, trans('commerce.order.stockNotEnough'));
            }
            $price = $commodityData['sale_price'];
            $totalPrice = bcmul($price, strval($quantity), 2);
            $actualPrice = $totalPrice;
            $discount = $commodityData['discount'];
            $discountPrice = '0';
            $discountReduction = '0';
            // 折扣
            if ($discount < 1 && $discount > 0) {
                $discountPrice = strval(
                    round(
                        floatval(bcmul(
                            bcmul($price, $discount, 4),
                            strval($quantity),
                            4
                        )),
                        2,
                        PHP_ROUND_HALF_UP
                    )
                );
                $discountReduction = bcsub($actualPrice, $discountPrice, 2);
                $actualPrice = $discountPrice;
            }
            $fullAmount = $commodityData['full_amount'];
            $fullReduction = $commodityData['full_reduction'];
            $reductionPrice = '0.00';
            // 满减
            if ($fullAmount > 0 && $fullReduction > 0) {
                $reductionTimes = bcdiv($actualPrice, $fullAmount, 0);
                $reductionPrice = bcadd(
                    $reductionPrice,
                    bcmul($fullReduction, $reductionTimes, 2),
                    2
                );
            }
            $actualPrice = bcsub($actualPrice, $reductionPrice, 2);
            if ($actualPrice < 0) {
                $actualPrice = '0.00';
            }
            $this->orderProductRepository->create([
                'order_id' => $orderID,
                'merchant_id' => $merchantId,
                'commodity_id' => $id,
                'commodity_name' => $commodityData['commodity_name'],
                'price' => $price,
                'quantity' => $quantity,
                'discount' => $discount,
                'reduction' => $reductionPrice,
                'total_price' => $actualPrice,
                'photo' => $commodityData['commodity_images'],
                'commodity_spec' => $commodityData['commodity_spec'],
                'commodity_unit' => $commodityData['commodity_unit'],
            ]);

            $order = $this->repository->create([
                'id' => $orderID,
                'merchant_id' => $merchantId,
                'customer_id' => $customer_id,
                'total_amount' => $totalPrice,
                'discount_amount' => $discountReduction,
                'minus_amount' => $reductionPrice,
                'actual_amount' => $actualPrice,
                'order_source' => $data['order_source'] ?? 1,
                'payment_method' => $data['payment_method'] ?? 5,
                'payment_time' => $actualPrice <= 0 ? date('Y-m-d H:i:s') : null,
                'order_status' => $actualPrice <= 0 ? CommerceOrder::PAID : CommerceOrder::UNPAID,
                'customer_name' => $data['customer_name'],
                'customer_phone' => $data['customer_phone'] ?? '',
                'customer_address' => $data['customer_address'] ?? '',
                'remark' => $data['remark'] ?? '',
            ]);
            Db::commit();
        } catch (BusinessException $e) {
            Db::rollBack();
            Common::logException(__CLASS__, $e);
            throw $e;
        } catch (\Throwable $e) {
            Db::rollBack();
            Common::logException(__CLASS__, $e);
            throw new BusinessException(ResultCode::UNPROCESSABLE_ENTITY, trans('commerce.order.orderError'));
        } finally {
            Common::unlock($lockKey, $orderID);
            $renewCoroutineRunning = false;
        }
        return $order->toArray();
    }

    public function apiPage(array $params, int $page = 1, int $pageSize = 10): array
    {
        $result = $this->repository->perQuery(
            $this->repository->getQuery()
                ->with(['commdities', 'merchant:id,merchant_name,contact_phone'])
                ->where('order_status', '!=', CommerceOrder::DELETED),
            $params
        )->paginate(
            perPage: $pageSize,
            pageName: $this->repository::PER_PAGE_PARAM_NAME,
            page: $page,
        );
        return $this->repository->handlePage($result);
    }

    public function cancelOrder(mixed $id, array $params)
    {
        $order = $this->repository->getQuery()->where('id', $id)
            ->where('customer_id', $params['customer_id'])
            ->get()
            ->first();
        if (!$order) {
            throw new BusinessException(ResultCode::NOT_FOUND, trans('commerce.order.notFound'));
        }
        if ($order->order_status == CommerceOrder::UNPAID) {
            return $this->repository->getQuery()
                ->where('id', $id)
                ->where('customer_id', $params['customer_id'])
                ->where('order_status', CommerceOrder::UNPAID)
                ->update([
                    'order_status' => CommerceOrder::CANCELED,
                    'cancel_reason' => $params['cancel_reason'] ?? trans('commerce.order.reasonsNotFilled'),
                    'cancel_time' => date('Y-m-d H:i:s'),
                ]) == 0 ? throw new BusinessException(ResultCode::UNPROCESSABLE_ENTITY, trans('commerce.order.cancelError')) : true;
        } else if ($order->order_status == CommerceOrder::CANCELED) {
            throw new BusinessException(ResultCode::UNPROCESSABLE_ENTITY, trans('commerce.order.canceled'));
        } else {
            throw new BusinessException(ResultCode::METHOD_NOT_ALLOWED, trans('commerce.order.contactCustomerService'));
        }
    }

    public function deleteOrder(array $data)
    {
        if (!isset($data['id'])) {
            throw new BusinessException(ResultCode::UNPROCESSABLE_ENTITY, trans('commerce.order.notSelect'));
        }
        return $this->repository->getQuery()
            ->where('id', $data['id'])
            ->where('customer_id', $data['customer_id'])
            ->whereIn('order_status', [CommerceOrder::UNPAID, CommerceOrder::CANCELED])
            ->update(
                [
                    'order_status' => CommerceOrder::DELETED,
                ]
            ) == 0 ? throw new BusinessException(ResultCode::UNPROCESSABLE_ENTITY, trans('commerce.order.deleteError')) : true;
    }

    public function deliverOrder(int $orderId, array $data)
    { 
        if (!isset($data['express_company']) || !isset($data['express_id'])) {
            throw new BusinessException(ResultCode::UNPROCESSABLE_ENTITY, trans('commerce.order.expressNotFilled'));
        }
        $admin_id = $data['admin_id'];
        $merchantId = $this->merchantRepository->getQuery()->where('admin_id', $admin_id)->first();
        if (!$merchantId) {
            throw new BusinessException(ResultCode::UNPROCESSABLE_ENTITY, trans('commerce.order.notFoundMerchant'));
        }
        $merchantId = $merchantId->id;
        return $this->repository->getQuery()->where('id', $orderId)
            ->where('merchant_id', $merchantId)
            ->where('order_status', CommerceOrder::PAID)
            ->update([
                'order_status' => CommerceOrder::SHIPPED,
                'delivery_time' => $data['delivery_time']?? date('Y-m-d H:i:s'),
                'express_company' => $data['express_company'],
                'express_id' => $data['express_id'],
                'express_cost' => $data['express_cost'] ?? 0,
            ]) == 0 ? throw new BusinessException(ResultCode::UNPROCESSABLE_ENTITY, trans('commerce.order.deliverError')) : true;
    }
}
